package com.gitee.wsl.func.filter.bloomfilter

import com.gitee.wsl.func.filter.bloomfilter.api.BitMapExtractor
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilter
import com.gitee.wsl.func.filter.bloomfilter.api.BloomFilter.Companion.SPARSE
import com.gitee.wsl.func.filter.bloomfilter.api.Hasher
import com.gitee.wsl.func.filter.bloomfilter.api.IndexExtractor
import com.gitee.wsl.func.filter.bloomfilter.bean.BitMaps
import com.gitee.wsl.func.filter.bloomfilter.bean.Shape
import com.gitee.wsl.func.predicate.IntPredicate
import com.gitee.wsl.func.predicate.LongPredicate
import com.gitee.wsl.func.predicate.Predicate
import com.gitee.wsl.text.format.format


/**
 * A bloom filter using a TreeSet of integers to track enabled bits. This is a standard
 * implementation and should work well for most low cardinality Bloom filters.
 *
 * @since 4.5.0-M1
 */
class SparseBloomFilter : BloomFilter<SparseBloomFilter> {
    /**
     * The bitSet that defines this BloomFilter.
     */
    //private val indices: TreeSet<Int>
    private val indices: MutableSet<Int>

    /**
     * The shape of this BloomFilter.
     */
    override val shape: Shape

    /**
     * Constructs an empty BitSetBloomFilter.
     *
     * @param shape The shape of the filter.
     */
    constructor(shape: Shape) {
        this.shape = shape
        this.indices = mutableSetOf()
    }

    private constructor(source: SparseBloomFilter) {
        shape = source.shape
        indices = mutableSetOf()
        indices.addAll(source.indices)
    }

    /**
     * Adds the index to the indices.
     *
     * @param idx the index to add.
     * @return `true` always
     */
    private fun add(idx: Int): Boolean {
        indices.add(idx)
        return true
    }

    public override fun asBitMapArray(): LongArray {
        val result: LongArray = BitMaps.newBitMap(shape)
        for (i in indices) {
            BitMaps.set(result, i)
        }
        return result
    }

    public override fun cardinality(): Int {
        return indices.size
    }

    public override fun characteristics(): Int {
        return SPARSE
    }

    public override fun clear() {
        indices.clear()
    }

    public override fun contains(bitMapExtractor: BitMapExtractor): Boolean {
        return contains(IndexExtractor.fromBitMapExtractor(bitMapExtractor!!))
    }

    public override fun contains(indexExtractor: IndexExtractor): Boolean {
        return indexExtractor.processIndices(indices::contains)
    }

    /**
     * Creates a new instance of this [SparseBloomFilter] with the same properties as the current one.
     *
     * @return a copy of this [SparseBloomFilter].
     */
    public override fun copy(): SparseBloomFilter {
        return SparseBloomFilter(this)
    }


    override fun isEmpty(): Boolean = indices.isEmpty()

    public override fun merge(bitMapExtractor: BitMapExtractor): Boolean {
        return this.merge(IndexExtractor.fromBitMapExtractor(bitMapExtractor))
    }

    public override fun merge(other: BloomFilter<*>): Boolean {
        val indexExtractor =
            if ((other.characteristics() and SPARSE) != 0) other as IndexExtractor else IndexExtractor.fromBitMapExtractor(
                other
            )
        merge(indexExtractor)
        return true
    }

    public override fun merge(hasher: Hasher): Boolean {
        merge(hasher.indices(shape))
        return true
    }

    public override fun merge(indexExtractor: IndexExtractor): Boolean {
        indexExtractor.processIndices(Predicate { idx: Int -> this.add(idx) })
        if (!indices.isEmpty()) {
            require(indices.last() < shape.numberOfBits) {
                String.format(
                    "Value in list %s is greater than maximum value (%s)",
                    indices.last(), shape.numberOfBits - 1
                )
            }
            require(indices.first() >= 0) {
                String.format(
                    "Value in list %s is less than 0",
                    indices.first()
                )
            }
        }
        return true
    }

    public override fun processBitMaps(predicate: LongPredicate): Boolean {
        val limit: Int = BitMaps.numberOfBitMaps(shape)
        //
        // because our indices are always in order we can shorten the time necessary to
        // create the longs for the consumer
        //
        // the currently constructed bitMap
        var bitMap: Long = 0
        // the bitmap we are working on
        var idx = 0
        for (i in indices) {
            while (BitMaps.getLongIndex(i) != idx) {
                if (!predicate.test(bitMap)) {
                    return false
                }
                bitMap = 0
                idx++
            }
            bitMap = bitMap or BitMaps.getLongBit(i)
        }
        // we fall through with data in the bitMap
        if (!predicate.test(bitMap)) {
            return false
        }
        // account for hte bitMap in the previous block + the next one
        idx++
        // while there are more blocks to generate send zero to the consumer.
        while (idx < limit) {
            if (!predicate.test(0L)) {
                return false
            }
            idx++
        }
        return true
    }

    public override fun processIndices(predicate: IntPredicate): Boolean {
        return indices.all(predicate::test)
    }
}
